package turn

import (
	"bufio"
	"bytes"
	"context"
	"encoding/gob"
	"errors"
	"gitee.com/yysf-xin/dproxy/pkg/tools"
	"golang.org/x/net/proxy"
	"net"
	"net/http"
	"net/http/httputil"
	"strings"
	"sync"
	"time"
)

func (c *Client) DialHTTP(action, path string) (conn net.Conn, err error) {
	if c.dialer == nil {
		conn, err = net.DialTimeout("tcp", c.Addr, 10*time.Second)
	} else {
		conn, err = c.dialer.Dial("tcp", c.Addr)
	}
	if err != nil {
		return
	}
	var buf bytes.Buffer
	buf.WriteString(http.MethodGet)
	buf.WriteString(" ")
	buf.WriteString(c.Path)

	if path != "" {
		if !strings.HasPrefix(path, "/") {
			buf.WriteString("/")
		}
		buf.WriteString(path)
	}

	buf.WriteString(" ")
	buf.WriteString("HTTP/1.1")
	buf.WriteString("\r\n")
	buf.WriteString("Connection: upgrade")
	buf.WriteString("\r\n")
	buf.WriteString("Upgrade: ")
	buf.WriteString(action)
	buf.WriteString("\r\n")
	buf.WriteString("Host: ")
	buf.WriteString(c.Host)
	buf.WriteString("\r\n")
	buf.WriteString("\r\n")

	//tools.Log.Print(buf.String())

	_, err = conn.Write(buf.Bytes())

	resp, err := http.ReadResponse(bufio.NewReaderSize(conn, 0xff), &http.Request{Method: http.MethodConnect})
	if err != nil {
		return
	}
	if err == nil && resp.StatusCode == http.StatusSwitchingProtocols {
		return
	}
	bd, err := httputil.DumpResponse(resp, false)
	if err == nil {
		err = errors.New(string(bd))
	} else {
		err = errors.New("unexpected HTTP response")
	}
	_ = conn.Close()
	return nil, err
}

var (
	UnknowErr = errors.New("unknown error")
)

func (c *Client) Serve(conn net.Conn) (err error) {
	if c.codec == nil {
		encBuf := bufio.NewWriter(conn)
		decBuf := bufio.NewReader(conn)
		c.codec = &ClientCodec{
			c.GroupId,
			conn,
			time.Now().UnixMilli(),
			encBuf,
			gob.NewEncoder(encBuf),
			decBuf,
			gob.NewDecoder(decBuf),
			nil,
			sync.Mutex{},
		}
	} else if c.codec.Conn != conn {
		c.codec.Conn = conn
		c.codec.w.Reset(conn)
		c.codec.r.Reset(conn)
		c.codec.enc = gob.NewEncoder(c.codec.w)
		c.codec.dec = gob.NewDecoder(c.codec.r)
	} else {
		return nil
	}
	tools.Log.Println(turnClientTag, "client connected")
	defer conn.Close()

	ctx, cancel := context.WithCancel(context.Background())

	defer cancel()
	go func() {
		tick := time.NewTicker(10 * time.Second)
		defer tick.Stop()
		for err == nil {
			select {
			case <-ctx.Done():
				return
			case <-tick.C:
				err = c.codec.Encode(PingCommand, &PingMsg{})
			}
		}
	}()

	r := &Request{}
	for err == nil {
		if err = c.codec.dec.Decode(r); err != nil {
			break
		}
		var msg ClientMsgHandler
		switch r.Command {
		case ConnectCommand:
			msg = &ConnectMsg{}
		case PingCommand:
			msg = &PingMsg{}
		}

		if msg != nil {
			err = c.codec.dec.Decode(msg)
		} else {
			err = UnknowErr
		}

		if msg != nil && err == nil {
			msg.ClientDo(c.codec, c)
		}
	}
	tools.Log.Println(turnClientTag, "end", err)
	return
}

func NewClient(address, path string, dialer proxy.Dialer) (*Client, error) {
	host, _, err := net.SplitHostPort(address)
	if err != nil {
		return nil, err
	}
	return &Client{
		Addr:   address,
		Path:   path,
		Host:   host,
		dialer: dialer,
	}, nil
}
